package com.platform.core.service.impl;

import com.google.common.base.Strings;
import com.platform.core.repository.SysRoleRepository;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Predicate;
import com.platform.comm.dto.DtoMapper;
import com.platform.comm.enums.YnEnum;
import com.platform.comm.exceptions.RestApiException;
import com.platform.comm.util.StringUtil;
import com.platform.dto.DepartQo;
import com.platform.dto.RoleQo;
import com.platform.core.entity.QSysRole;
import com.platform.core.entity.QSysUserRole;
import com.platform.core.entity.SysRole;
import com.platform.enums.RangeType;
import com.platform.core.security.auth.AuthenticationFacade;
import com.platform.core.service.SysDepartService;
import com.platform.core.service.SysRoleFunctionService;
import com.platform.core.service.SysRoleService;
import com.platform.core.service.SysUserRoleService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QSort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.platform.comm.constants.GlobalConstant.BASE_REDIS_CACHE_PREFIX;

/**
 * Description: 角色-Service接口
 *
 * @author libin Created on 2019/7/15
 **/
@Service
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class SysRoleServiceImpl extends BaseEntityBizImpl<SysRoleRepository, SysRole> implements SysRoleService {

    private final QSysRole qSysRole = QSysRole.sysRole;
    private final QSysUserRole qSysUserRole = QSysUserRole.sysUserRole;
    private final DtoMapper dtoMapper;
    private final SysUserRoleService sysUserRoleService;
    private final SysRoleFunctionService sysRoleFunctionService;
    private final SysDepartService sysDepartService;

    public SysRoleServiceImpl(SysRoleRepository baseRepository, DtoMapper dtoMapper,
                              SysUserRoleService sysUserRoleService, SysRoleFunctionService sysRoleFunctionService,
                              SysDepartService sysDepartService) {
        super(baseRepository);
        this.dtoMapper = dtoMapper;
        this.sysUserRoleService = sysUserRoleService;
        this.sysRoleFunctionService = sysRoleFunctionService;
        this.sysDepartService = sysDepartService;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public RoleQo create(RoleQo roleQo) {
        // 初始化数据
        roleQo.init();
        SysRole sysRole = dtoMapper.map(roleQo, SysRole.class);
        boolean checkDepartId =
                Objects.nonNull(roleQo.getRangeType()) && !RangeType.A.equals(roleQo.getRangeType()) && StringUtils.isEmpty(roleQo.getDepartId());
        if (checkDepartId) {
            throw new RestApiException("非全局范围时组织不能为空");
        }
        // 设置route
        if (Strings.isNullOrEmpty(roleQo.getParentId())) {
            sysRole.setRoute(roleQo.getCode());
        } else {
            sysRole.setRoute(generateRoute(roleQo.getParentId()) + ROUTE_SEPARATOR + roleQo.getCode());
        }
        sysRole.setId(StringUtil.getUUID());
        saveEntity(sysRole);
        return dtoMapper.map(sysRole, RoleQo.class);
    }

    private String generateRoute(String parentId) {
        return fetchById(parentId).getRoute();
    }

    @Caching(
            evict = {
                    @CacheEvict(value = BASE_REDIS_CACHE_PREFIX + "user", allEntries = true),
                    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions"}, allEntries = true)
            }
    )
    @Transactional(rollbackFor = Exception.class)
    @Override
    public RoleQo modify(RoleQo roleQo) {
        Assert.notNull(roleQo.getId(), "修改时唯一标识符不能为空");
        if (Objects.nonNull(roleQo.getParentId()) && roleQo.getId().equals(roleQo.getParentId())) {
            throw new RestApiException("继承的父角色不能是当前角色");
        }
        SysRole entity = fetchById(roleQo.getId());
        dtoMapper.map(roleQo, entity);
        entity.setUpdateDate(LocalDateTime.now());

        // 设置route
        if (Strings.isNullOrEmpty(roleQo.getParentId())) {
            entity.setRoute(roleQo.getCode());
        } else {
            entity.setRoute(generateRoute(roleQo.getParentId()) + ROUTE_SEPARATOR + roleQo.getCode());
        }

        saveEntity(entity);

        return dtoMapper.map(entity, RoleQo.class);
    }

    @Override
    public Page<RoleQo> queryRoles(Predicate predicate, Pageable pageable, MultiValueMap<String, String> parameters) {
        BooleanBuilder builder = builderConditions(predicate, parameters, SysRole.class, qSysRole.getMetadata());
        String roleName = parameters.getFirst("name");
        if (StringUtils.isNotBlank(roleName)) {
            builder.and(qSysRole.name.contains(roleName));
        }
        String code = parameters.getFirst("code");
        if (StringUtils.isNotBlank(code)) {
            builder.and(qSysRole.code.contains(code));
        }
        String appId = parameters.getFirst("appId");
        if (StringUtils.isNotBlank(appId)) {
            builder.and(qSysRole.appId.eq(appId));
        }

        // 添加一个开关量。用作对列表界面和弹窗界面中权限的过滤
        String switchType = Objects.toString(parameters.getFirst("switchType"), "1");
        if ("1".equals(switchType)) {
            filterRolesByCurrentDepart(builder, parameters);
        } else {
            addRangType(builder, parameters);
        }
        if (pageable.getSort().isUnsorted()) {
            pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), QSort.by(qSysRole.createDate.desc()));
        }
        QSysRole parent = new QSysRole("r");
        Page<Tuple> tuplePage = repository.findAll(builder, pageable,
                jpqlQuery -> jpqlQuery.leftJoin(parent).on(qSysRole.parentId.eq(parent.id)), qSysRole, parent.name);

        return tuplePage.map(tuple -> {
            RoleQo roleQo = dtoMapper.map(tuple.get(qSysRole), RoleQo.class);
            if (StringUtils.isNotEmpty(roleQo.getDepartId())) {
                DepartQo departQo = sysDepartService.findById(roleQo.getDepartId());
                roleQo.setDepartName(departQo.getDepartName());
            }
            roleQo.setParentName(tuple.get(parent.name));
            return roleQo;
        });
    }

    /**
     * 2020-12-23 角色从kafka 同步过来 导致角色没有所属部门关系和范围类型的等信息
     * 故此逻辑暂时废除
     * <p>
     * 显示基础列表时，按照当前用户所属部门添加角色的过滤条件
     * <p>
     * 如果是区域,需要查询部门表，查出当前组织和其下级管辖组织，将查询出的departIds传入角色表中进行过滤。
     * 如果是机构或部门，只显示当前机构/部门的角色，以及全局角色
     */
    private void filterRolesByCurrentDepart(BooleanBuilder builder, MultiValueMap<String, String> parameters) {
       /* String currentId = AuthenticationFacade.getDepartId();
        DepartQo depart = sysDepartService.findById(currentId);
        if (Objects.nonNull(depart)) {
            String kind = depart.getDepartKind();
            if (kind.equals(DepartType.DOMAIN.name())) {
                List<String> ids = sysDepartService.getDepartIdsWithDomainByCurrent(currentId);
                builder.and(qSysRole.departId.in(ids));
            } else {
                builder.and(qSysRole.departId.eq(currentId));
            }
        }
        String name = parameters.getFirst("name");
        String code = parameters.getFirst("code");
        if (StringUtils.isNotEmpty(name) && StringUtils.isEmpty(code)) {
            builder.or(qSysRole.rangeType.eq(RangeType.A.name())
                    .and(qSysRole.name.like("%" + name + "%")));
        } else if (StringUtils.isNotEmpty(code) && StringUtils.isEmpty(name)) {
            builder.or(qSysRole.rangeType.like("%" + RangeType.A.name() + "%")
                    .and(qSysRole.code.eq(code)));
        } else if (StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(code)) {
            builder.or(qSysRole.rangeType.eq(RangeType.A.name()).and(qSysRole.code.eq(code))
                    .and(qSysRole.name.like("%" + name + "%")));
        } else if (StringUtils.isEmpty(name) && StringUtils.isEmpty(code)){
            builder.or(qSysRole.rangeType.eq(RangeType.A.name()));
        }*/
    }

    /**
     * 2020-12-23 角色从kafka 同步过来 导致角色没有所属部门关系和范围类型的等信息
     * 故此逻辑暂时废除
     * <p>
     * 这里是用户授权时弹窗中的数据过滤。需传当前选择用户的所属部门id
     * <p>
     * 根据部门id，查询部门的类型
     * 如果是区域，只能选择本区域角色和全局角色。
     * 如果是机构，只能选择本机构角色和全局角色。
     * 如果是部门，只能选择本全局角色。
     */
    private void addRangType(BooleanBuilder builder, MultiValueMap<String, String> parameters) {
      /*  String deptId = parameters.getFirst("deptId");
        Assert.notNull(deptId, "所属组织不存在");
        DepartQo depart = sysDepartService.findById(deptId);
        if (Objects.nonNull(depart)) {
            String kind = depart.getDepartKind();
            if (kind.equals(DepartType.DOMAIN.name())) {
                builder.and(qSysRole.rangeType.eq(RangeType.D.name()).and(qSysRole.departId.eq(deptId)));
                builder.or(qSysRole.rangeType.eq(RangeType.A.name()));
            } else if (kind.equals(DepartType.NORMAL.name())) {
                builder.and(qSysRole.rangeType.in(RangeType.O.name()).and(qSysRole.departId.eq(deptId)));
                builder.or(qSysRole.rangeType.eq(RangeType.A.name()));
            } else {
                builder.and(qSysRole.rangeType.in(RangeType.A.name()));
            }
        }*/
    }

    @Override
    public boolean checkCodeExists(String id, String code) {
        BooleanBuilder builder = buildValidConditions(id);
        builder.and(qSysRole.code.eq(code));
        return repository.count(builder) > 0;
    }

    @Override
    public boolean checkNameExists(String id, String roleName) {
        BooleanBuilder builder = buildValidConditions(id);
        builder.and(qSysRole.name.eq(roleName));
        return repository.count(builder) > 0;
    }

    private BooleanBuilder buildValidConditions(String id) {
        BooleanBuilder builder = new BooleanBuilder();
        if (!Strings.isNullOrEmpty(id)) {
            builder.and(qSysRole.id.ne(id));
        }
        return builder;
    }

    @Override
    public SysRole findByCode(String code) {
        Assert.notNull(code, "角色编码不能为空");
        return fetchOne(qSysRole.code.eq(code));
    }

    @Override
    public SysRole findByCodeNoException(String code) {
        Assert.notNull(code, "角色编码不能为空");
        return repository.findOne(qSysRole.code.eq(code)).orElse(null);
    }

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void changeDelFlag(List<String> ids, YnEnum ynEnum) {
        ids.stream().map(this::fetchById)
                .forEach(sysRole -> {
                    sysRole.setDelFlag(ynEnum);
                    sysRole.setUpdateDate(LocalDateTime.now());
                });
        repository.flush();
    }

    @Caching(
            evict = {
                    @CacheEvict(value = BASE_REDIS_CACHE_PREFIX + "user", allEntries = true),
                    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions"}, allEntries = true)
            }
    )
    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void remove(String id) {
        // 判断当前角色是否有用户使用，若有，则不可以删除
        BooleanBuilder builder = new BooleanBuilder();
        long count = sysUserRoleService.count(builder.and(qSysUserRole.roleId.eq(id)));
        if (count > 0) {
            throw new RestApiException("当前角色已有用户使用，请先解除用户角色关系！");
        }
        delete(id);
    }

    @Override
    public void delete(String id) {
        preDelete(id);
        repository.deleteById(id);
    }

    private void preDelete(String roleId) {
        // 删除角色时，判断是否有子节点
        long count = repository.count(qSysRole.parentId.eq(roleId));
        if (count > 0) {
            throw new RestApiException("当前角色被其他角色引用为父角色，请先解除角色继承关系！");
        }
        // 删除关联角色
        sysUserRoleService.removeByRoleId(roleId);
        // 删除管理菜单及权限
        sysRoleFunctionService.removeByRoleId(roleId);
    }

    @Override
    public RoleQo getByRoleId(String roleId) {
        return dtoMapper.map(fetchById(roleId), RoleQo.class);
    }

    @Override
    public List<RoleQo> getByRoleId(List<String> roleIds) {
        List<SysRole> list = queryAll(qSysRole.id.in(roleIds));
        return list.stream().map(sysRole -> dtoMapper.map(sysRole, RoleQo.class)).collect(Collectors.toList());
    }

    @Override
    public List<RoleQo> queryValidRoles(String departId) {
        List<SysRole> list = queryAll(qSysRole.departId.eq(departId).or(qSysRole.rangeType.eq(RangeType.A.name())));
        return list.stream().map(sysRole -> dtoMapper.map(sysRole, RoleQo.class)).collect(Collectors.toList());
    }

    @Override
    public Set<String> roles() {
        // 1. 获取用户权限
        Set<String> authorizes =
                org.springframework.util.StringUtils.commaDelimitedListToSet(AuthenticationFacade.getUser().getAuth());
        return authorizes;
    }

    @Override
    public void syncSubsystem(String id) {
        // todo
    }


    @Caching(
            evict = {
                    @CacheEvict(value = BASE_REDIS_CACHE_PREFIX + "user", allEntries = true),
                    @CacheEvict(value = {BASE_REDIS_CACHE_PREFIX + "roleFunctions"}, allEntries = true)
            }
    )
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void changeStatus(String id, String isEnable) {
        SysRole sysRole = fetchById(id);
        sysRole.setIsEnable(isEnable);
        sysRole.setUpdateDate(LocalDateTime.now());
        repository.flush();
    }

    @Override
    public List<RoleQo> queryRoleByPrefix(Map<String, String> parametersMap) {
        String prefix = parametersMap.get("prefix");
        Assert.isTrue(!Strings.isNullOrEmpty(prefix), "前缀信息不能为空");
        return queryAll(qSysRole.code.like(prefix + "%")).stream().map(sysRole -> dtoMapper.map(sysRole, RoleQo.class))
                .collect(Collectors.toList());
    }


}
